/*
 *
 * Copyright 2011 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

Components.utils.import("resource://oaa-cache-inspector/cache-inspector-common.js");

/* ---------------------------------------------------------------- */
/*                  Initialize namespace properties                 */
/* ---------------------------------------------------------------- */

if (typeof OAA_CI == "undefined") {
  var OAA_CI = {};
}

/**
 * @function removeChildNodes
 *
 * @desc removes the child nodes of a DOM object
 *
 * @param {Object} node  - Object DOM node object
 */

OAA_CI.removeChildNodes = function (node) {

  if (node && node.hasChildNodes()) {
        while (node.firstChild) {
          node.removeChild(node.firstChild );
        }
  }

};

/**
 * @function setTreeColumnLabels
 *
 * @memberOf OAA_CI
 *
 * @desc creates a XUL element with the specified attributes
 *
 * @param {XULTree}  tree       - XUL tree object to update column labels
 * @param {Array}    column_labels  - array of Strings or Objects
 *                                    if String it is the cell content
 *                                    if Object it contains the following properties: 'label', 'style' and 'tooltip'
 *
 * @param {Array}    flex_values    - array of Numbers used as the flex value for each column
 *
 * @return {XULChildren}  of XUL tree
 */

OAA_CI.setTreeColumnLabels = function(tree, column_labels, flex_values) {
    
    var flex;
    var column_label;
    var treecol;
    var splitter;
    
    var column_labels_len = column_labels.length;

    OAA_CI.removeChildNodes(tree);

    var treecols = document.createElement('treecols');

    for (var i=0; i < column_labels_len; i++ ) {

      flex         = flex_values[i];
      column_label = column_labels[i];

      treecol = document.createElement('treecol');
      treecol.setAttribute('flex', flex);
      
      if (typeof column_label == 'string') {
        treecol.setAttribute('label', column_label );
      }
      else {
        if (column_label.label)   treecol.setAttribute('label',       column_label.label);
        if (column_label.style)   treecol.setAttribute('properties',  column_label.style);
        if (column_label.tooltip) treecol.setAttribute('tooltiptext', column_label.tooltip);        
      }
      treecols.appendChild(treecol);

      splitter = document.createElement('splitter');
      splitter.setAttribute('class', 'tree-splitter');
      treecols.appendChild(splitter);

    } // end loop

    tree.appendChild(treecols);

    var treechildren = document.createElement('treechildren');
    treechildren.id = 'ID_CACHE_ITEMS_CHILDREN';
    tree.appendChild(treechildren);

    return treechildren;

};

/**
 * @function addTreeRow
 *
 * @memberOf OAA_CI
 *
 * @desc Adds a row to the treechildren node of a XUL tree
 *
 * @param  {XULElement}  treechildren - XUL treechildren node object to add the row
 * @param  {Array}       items        - array of items to display in the the row
 * @param  {String}      cache_id     - Used to set the value attribute of the treerow to 
 *                                      identify the cache item represented by the row
 */

OAA_CI.addTreeRow = function (treechildren, items, cache_id) {

  var i;
  var items_len = items.length;
  var item;
  
  var treerow;
  var treecell;

  treerow = document.createElement('treerow');

  for (i = 0; i < items_len; i++) {
  
    item = items[i];

    if (!item) {
      if (item === 0) 
        item = "0";
      else
        item = "";
    }    

    treecell = document.createElement('treecell');

    if (i == 0 && cache_id && cache_id.length) treecell.setAttribute('value', cache_id);

    if (typeof item == 'object') {
      if (item.label) treecell.setAttribute('label',       item.label);
      if (item.style) treecell.setAttribute('properties',  item.style);
    }
    else {
      treecell.setAttribute('label', item);
    }
    
    
    treecell.setAttribute('crop', 'end');

    treerow.appendChild(treecell);

  } // end loop

  treechildren.appendChild(treerow);

};

/**
 * @function addTreeItem
 *
 * @memberOf OAA_CI
 *
 * @desc Add a cache element object to a row in a tree
 *
 * @param  {Object} treechidren  - DOM node whos children contain XUL treeitems
 * @param  {Object} cache_item   - object in a list of cache objects
 * @param  {Array}  properties   - array of property names to display in the row
 * @param  {String} value        - if defined sets the value property of the treecell with string
 *
 * @return
 */

OAA_CI.addTreeItem = function (treechildren, cache_item, properties, value) {

  var treeitem = document.createElement('treeitem');

  OAA_CI.addTreeRow(treeitem, cache_item, properties, value);

  treechildren.appendChild(treeitem);

};

OAA_CI.addEmptyRow = function (treechildren) {

  var treeitem = document.createElement('treeitem');

  OAA_CI.addTreeRow(treeitem, ['none'], 'none');

  treechildren.appendChild(treeitem);

};

OAA_CI.addTransitionRow = function (treechildren) {

  var treeitem = document.createElement('treeitem');

  OAA_CI.addTreeRow(treeitem, ['loading']);

  treechildren.appendChild(treeitem);

};

/* ---------------------------------------------------------------- */
/*                       populateTree functions                     */
/* ---------------------------------------------------------------- */

/**
 * @function populateTreeAsFlatList
 *
 * @memberOf OAA_CI
 *
 * @desc Given a list of cache item elements and a XUL treechildren ID,
 *       insert each element as a treeitem with display properties that
 *       are specified on an element-specific basis.
 *
 * @param  {Array}  cache_items - The list of cache elements to be inserted into the
 *                                XUL tree.
 *
 * @param  {String} xtc_id      - The XUL ID of the top-level treechildren component.
 */

OAA_CI.populateTreeAsFlatList = function (cache_items, xtc_id) {

  var start_time = new Date().getTime();

  var treechildren = document.getElementById(xtc_id);
  OAA_CI.removeChildNodes(treechildren);

  var i; // loop counter
  var cache_item_len = cache_items.length; // loop control
  var cache_item;
  var props;

  if (cache_items.length == 0) {
    OAA_CI.addEmptyRow(treechildren);
  }
  else {
    for (i = 0; i < cache_item_len; i++) {
      cache_item = cache_items[i];
      props = cache_item.getDisplayProperties(xtc_id);
      OAA_CI.addTreeItem(treechildren, props, cache_item.cache_id);
    }
  }

  OAA_CI.updateXULTime(start_time);

};

/**
 * @function hasElementChildren
 *
 * @memberOf OAA_CI
 *
 * @desc Return a boolean value indicating whether element has children
 *       that are elements. The has_element_children property is found
 *       on the DOMElement object.
 *
 * @param {Object}  cache_item  - An OAA cache item element or DOMElement
 * 
 * @return  {Boolean}  True if element has children, false if not  
 */

OAA_CI.hasElementChildren = function (cache_item) {
  if (typeof cache_item.has_element_children === 'undefined')
    return cache_item.dom_element.has_element_children;
  else
    return cache_item.has_element_children;
};

/**
 * @function addElementChildren
 *
 * @memberOf OAA_CI
 *
 * @desc Recursively add elements as treeitems with display properties that
 *       are specified on an element-specific basis.
 *
 * @see populateTreeAsHierarchy
 */

OAA_CI.addElementChildren = function (cache_items, treechildren, xtc_id) {

  var i;  // loop counter
  var cache_items_len = cache_items.length; // loop control
  var cache_item; // loop placeholder
  var props;
  
  var sub_cache_items;
  var sub_cache_items_len;
  var sub_cache_item;
  
  var has_sub_cache_items;
  
  var treeitem;
  var sub_treechildren;

  for (i = 0; i < cache_items_len; i++) {
  
    cache_item = cache_items[i];
    
    if ((typeof cache_item.viewPredicate === 'function') && 
        !(cache_item.viewPredicate()))
      continue;

    props = cache_item.getDisplayProperties(xtc_id);
    
    treeitem = document.createElement('treeitem');
    
    OAA_CI.addTreeRow(treeitem, props, cache_item.cache_id);

    sub_cache_items = cache_item.getChildren();
    
    has_sub_cache_items = OAA_CI.hasElementChildren(cache_item);
    
    if (has_sub_cache_items && 
        sub_cache_items && 
        sub_cache_items.length) {
        
      treeitem.setAttribute('container', 'true');
      treeitem.setAttribute('open', 'false');

      sub_treechildren = document.createElement('treechildren');
      
      OAA_CI.addElementChildren(sub_cache_items, sub_treechildren, xtc_id);
      
      treeitem.appendChild(sub_treechildren);
    }
    
    treechildren.appendChild(treeitem);
    
  }
};

/**
 * populateTreeAsHierarchy
 *
 * @desc Given a list of cache item elements and a XUL treechildren ID,
 *       insert each element and its children into the tree.
 *
 * @param elements - A list of elements, each potentially serving as
 *        the root of a subtree of elements.
 *
 * @param xtc_id - The XUL ID of the top-level treechildren component.
 *
 * @see  addElementChildren
 */

OAA_CI.populateTreeAsHierarchy = function (elements, xtc_id) {

  var start_time = new Date().getTime();

  var treechildren = document.getElementById(xtc_id);
  OAA_CI.removeChildNodes(treechildren);

  if (elements.length == 0) {
    OAA_CI.addEmptyRow(treechildren);
  }
  else {
    OAA_CI.addElementChildren(elements, treechildren, xtc_id);
  }

  OAA_CI.updateXULTime(start_time);

};

/**
 * @function populateTreeAsTwoLevelList
 *
 * @memberOf OAA_CI
 *
 * @desc This function is for collections of dom cache elements with shared properties
 * 
 * @param  {Array}   cache_items - A list of OAA cache items, each containing a flat
 *                                 list of child elements.
 * @param  {String}  xtc_id      - The XUL ID of the top-level treechildren component.
 */

OAA_CI.populateTreeAsTwoLevelList = function (cache_items, xtc_id) {

  var i; // loop counter
  var j; // loop counter
  
  var cache_items_len = cache_items.length; // loop control
  var cache_item; // loop placeholder
  var props;
  
  
  var sub_cache_items;
  var sub_cache_items_len;
  var sub_cache_item;
  var sub_props;
  
  var treechildren;
  var treeitem;
  var sub_treechildren;
  
  var cs;
  
  var start_time = new Date().getTime();

  treechildren = document.getElementById(xtc_id);
  
  OAA_CI.removeChildNodes(treechildren);

  if (cache_items_len == 0) {
    OAA_CI.addEmptyRow(treechildren);
  }
  else {
    for (i = 0; i < cache_items_len; i++) {
    
      cache_item = cache_items[i];
      
      props    = cache_item.getDisplayProperties();
      treeitem = document.createElement('treeitem');
      treeitem.setAttribute('container', 'true');
      treeitem.setAttribute('open', 'false');
      
      OAA_CI.addTreeRow(treeitem, props, cache_item.cache_id);

      sub_treechildren = document.createElement('treechildren');
      sub_cache_items  = cache_item.getChildren();
      sub_cache_items_len  = sub_cache_items.length;

      for (j = 0; j < sub_cache_items_len; j++) {
        sub_cache_item = sub_cache_items[j];
        
        switch (xtc_id) {
        case 'ID_TREECHILDREN_ABBREVIATIONS':
          sub_props = [sub_cache_item.tag_name, cache_item.abbreviation_text, sub_cache_item.title, sub_cache_item.getAccessibility()];
          break;
          
        case 'ID_TREECHILDREN_LANGUAGES':
          sub_props = [sub_cache_item.tag_name, cache_item.lang, cache_item.getText(), sub_cache_item.getAccessibility()];
          break;
          
        case 'ID_TREECHILDREN_COLOR_CONTRAST': // children are DOMElement objects
          cs = sub_cache_item.computed_style;
          
          var cache_nls = OpenAjax.a11y.cache_nls;

          var is_large_font = cache_nls.getBooleanNLS(cs.is_large_font);

          var has_background_image = cache_nls.getBooleanNLS(cs.background_image != "none");
  
          var color_contrast_summary = sub_cache_item.getColorContrastSummary();

          sub_props = [sub_cache_item.tag_name, cs.color_hex, cs.background_color_hex, is_large_font, has_background_image, cs.color_contrast_ratio, color_contrast_summary];
          break;
          
        case 'ID_TREECHILDREN_DUPLICATE_HREF': // children are LinkElement objects
          sub_props = ['', sub_cache_item.href, sub_cache_item.name];
          break;
          
        case 'ID_TREECHILDREN_DUPLICATE_NAME': // children are LinkElement objects
          sub_props = ['', sub_cache_item.name, sub_cache_item.href];
          break;
          
        default:
          sub_props = [];
          break;
        }
        OAA_CI.addTreeItem(sub_treechildren, sub_props, sub_cache_item.cache_id);
      }

      treeitem.appendChild(sub_treechildren);
      treechildren.appendChild(treeitem);
    }
  }

  OAA_CI.updateXULTime(start_time);

};

/**
 * @function hasFilteredChildren
 *
 * @desc Determine whether an element has any child elements for
 *       which the filter function returns false.
 *
 * @param {Object}   element  - The element whose child elements are to be inspected.
 * @param {Boolean}  filter   - A boolean function used to filter out elements.
 */

OAA_CI.hasFilteredChildren = function (cache_item, filter) {

  var child_cache_items     = cache_item.getChildren();
  var child_cache_items_len = child_cache_items.length;
  var cache_item;
  
  for (var i = 0; i < child_cache_items_len; i++) {
    cache_item = child_cache_items[i];
    if (!filter(cache_item)) return true;
  }
  
  return false;

};

/**
 * @function addFilteredChildren
 *
 * @memberOf OAA_CI
 *
 * @desc Recursively add elements as treeitems, provided they are not
 *       filtered by the filter function, with display properties that
 *       are specified on an element-specific basis.
 *
 * @param  {Array}       cache_items   - The child elements to be added to the tree.
 * @param  {XULElement}  treechildren  - The XUL treechildren component currently being populated.
 * @param  {String}      xtc_id        - The XUL ID of the top-level treechildren component.
 * @param  {Boolean}     filter        - A boolean function to be used as an element filter.
 *
 * @see populateTreeAsTwoLevelFilteredHierarchy
 */

OAA_CI.addFilteredChildren = function (cache_items, treechildren, xtc_id, filter) {

  var i;  // loop counter
  
  var cache_items_len = cache_items.length; // loop control
  var cache_item; // loop placeholder
  var props;
  
  var child_cache_items;
  var child_cache_items_len;
  
  var has_filtered_children;
  
  var treeitem;
  var sub_treechildren;

  for (i = 0; i < cache_item_len; i++) {
    cache_item = cache_items[i];
    if (filter(cache_item)) continue;

    props = cache_item.getDisplayProperties(xtc_id);
    treeitem = document.createElement('treeitem');
    
    OAA_CI.addTreeRow(treeitem, props, cache_item.cache_id);

    child_cache_items     = cache_item.getChildren();
    child_cache_items_len = child_cache_items.length;
    
    has_filtered_children = OAA_CI.hasFilteredChildren(cache_item, filter);
    
    if (has_filtered_children && 
        child_cache_items && 
        child_cache_items_len) {
        
      treeitem.setAttribute('container', 'true');
      treeitem.setAttribute('open', 'false');

      sub_treechildren = document.createElement('treechildren');
      
      OAA_CI.addFilteredChildren(child_elements, sub_treechildren, xtc_id, filter);
      
      treeitem.appendChild(sub_treechildren);
    }
    treechildren.appendChild(treeitem);
  }

};

/**
 * @function populateTreeAsTwoLevelFilteredHierarchy
 *
 * @memberOf OAA_CI
 *
 * @desc Given a list of cache item elements and a XUL treechildren ID,
 *       insert the top-level elements as an unfiltered list where each
 *       element may contain a hierarchy of elements that are filtered
 *       using the provided filter function.
 *
 * @param {Array}   cache_items  - A list of cache elements of homogeneous type
 *                                 representing the top-level list in the tree. Each of these
 *                                 elements is expected to contain element children that will be
 *                                 displayed hierarchically.
 *
 * @param {String}   xtc_id      - The XUL ID of the top-level treechildren component.
 * @param {Boolean}  filter      - A boolean function to be used as an element filter.
 *
 * @see addFilteredChildren
 */

OAA_CI.populateTreeAsTwoLevelFilteredHierarchy = function (cache_items, xtc_id, filter) {

  var i; // loop counter
  var cache_items_len = cache_items.length; // loop control
  var cache_item; // loop placeholder
  var props;
  
  var child_cache_items;
  var child_cache_items_len;

  var treechildren;
  var treeitem;
  
  var sub_treechildren;

  var has_filtered_children;

  var start_time = new Date().getTime();

  treechildren = document.getElementById(xtc_id);
  
  OAA_CI.removeChildNodes(treechildren);

  if (cache_items_len == 0) {
    OAA_CI.addEmptyRow(treechildren);
  }
  else {
    for (i = 0; i < cache_items_len; i++) {
    
      cache_item = cache_items_lens[i];
      
      props = element.getDisplayProperties(xtc_id);

      treeitem = document.createElement('treeitem');
      OAA_CI.addTreeRow(treeitem, props, cache_item.cache_id);

      child_cache_items     = cache_item.getChildren();
      child_cache_items_len = child_cache_items.length;
      
      has_filtered_children = OAA_CI.hasFilteredChildren(element, filter);
      
      if (has_filtered_children && 
          child_cache_items && 
          child_cache_items_len) {
          
        treeitem.setAttribute('container', 'true');
        treeitem.setAttribute('open', 'false');

        sub_treechildren = document.createElement('treechildren');
        
        OAA_CI.addFilteredChildren(child_cache_items, sub_treechildren, xtc_id, filter);
        
        treeitem.appendChild(sub_treechildren);
      }
      treechildren.appendChild(treeitem);
    }
  }
  OAA_CI.updateXULTime(start_time);

};

/* ---------------------------------------------------------------- */
/*                    Tree Manipulation Functions                   */
/* ---------------------------------------------------------------- */

/**
 * @function expandTree
 *
 * @memberOf expandTree
 *
 * @desc Expand all container treeitems that are closed.
 *
 * @param {String} tree_id - ID of a XUL tree
 */

OAA_CI.expandTree = function (tree_id) {
  var tree = document.getElementById(tree_id);
  var tree_view = tree.view;

  for (var i = 0; i < tree_view.rowCount; i++) {
    if (tree_view.isContainer(i) && !tree_view.isContainerOpen(i))
      tree_view.toggleOpenState(i);
  }

};

/**
 * @function collapseTree
 *
 * @memberOf OAA_CI
 *
 * @desc Collapse all container treeitems that are open.
 *
 * @note It does not work to start at the first row because
 *       the rowCount property is based on the view, and
 *       after each row is collapsed, the count changes.
 *
 * @param {String} tree_id - ID of a XUL tree
 */

OAA_CI.collapseTree = function (tree_id) {
  var tree = document.getElementById(tree_id);
  var tree_view = tree.view;

  for (var i = (tree_view.rowCount - 1); i >= 0; i--) {
    if (tree_view.isContainer(i) && 
        tree_view.isContainerOpen(i))
      tree_view.toggleOpenState(i);
  }

};

